
using System;
using System.ComponentModel;
using System.Data.Linq;
using System.Linq;
using System.Runtime.Serialization;

namespace Friends.Data
{
    /// <summary>
    /// A base class for Linq entities that implements notification events.
    /// </summary>
    [DataContract( IsReference=true )]
    public abstract partial class LinqEntityBase :
        INotifyPropertyChanging,
        INotifyPropertyChanged
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="LinqEntityBase"/> class.
        /// </summary>
        protected LinqEntityBase()
        { }

        #region Notification Events

        /// <summary>
        /// Implements a PropertyChanged event.
        /// </summary>
        [field: NonSerialized]
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Raise the PropertyChanged event for a specific property.
        /// </summary>
        /// <param name="propertyName">Name of the property that has changed.</param>
        [EditorBrowsable(EditorBrowsableState.Advanced)]
        protected virtual void SendPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        /// <summary>
        /// Implements a PropertyChanging event.
        /// </summary>
        [field: NonSerialized]
        public event PropertyChangingEventHandler PropertyChanging;

        /// <summary>
        /// Raise the PropertyChanging event for a specific property.
        /// </summary>
        /// <param name="propertyName">Name of the property that is changing.</param>
        [EditorBrowsable(EditorBrowsableState.Advanced)]
        protected virtual void SendPropertyChanging(string propertyName)
        {
            if (PropertyChanging != null)
                PropertyChanging(this, new PropertyChangingEventArgs(propertyName));
            
        }
        #endregion
        
        #region Detach Methods
        /// <summary>
        /// Gets a value indicating whether this instance is attached to the change tracking of <see cref="DataContext"/>.
        /// </summary>
        /// <returns>
        /// <c>true</c> if this instance is attached; otherwise, <c>false</c>.
        /// </returns>
        public bool IsAttached()
        {
            return PropertyChanging != null;
        }
        
        /// <summary>
        /// Detach this instance from the <see cref="DataContext"/>.
        /// </summary>
        /// <remarks>
        /// Detaching the entity will allow it to be attached to another <see cref="DataContext"/>.
        /// </remarks>
        public virtual void Detach()
        {
            PropertyChanging = null;
            PropertyChanged = null;
        }
        
        /// <summary>
        /// Detach the specified <see cref="T:System.Data.Linq.EntitySet`1" />.
        /// </summary>
        /// <typeparam name="TEntity">The type of the entity.</typeparam>
        /// <param name="set">The <see cref="T:System.Data.Linq.EntitySet`1" /> to detach.</param>
        /// <param name="onAdd">Delegate for <see cref="M:System.Data.Linq.EntitySet`1.Add(`0)" />.</param>
        /// <param name="onRemove">Delegate for <see cref="M:System.Data.Linq.EntitySet`1.Remove(`0)" />.</param>
        /// <returns>A new <see cref="T:System.Data.Linq.EntitySet`1" /> with the list copied if it was loaded.</returns>
        protected static EntitySet<TEntity> Detach<TEntity>(EntitySet<TEntity> set, Action<TEntity> onAdd, Action<TEntity> onRemove) 
            where TEntity : LinqEntityBase
        {
            if (set == null || !set.HasLoadedOrAssignedValues)
                return new EntitySet<TEntity>(onAdd, onRemove);

            // copy list and detach all entities
            var list = set.ToList();
            list.ForEach(t => t.Detach());

            var newSet = new EntitySet<TEntity>(onAdd, onRemove);
            newSet.Assign(list);
            return newSet;
        }

        /// <summary>
        /// Detach the specified <see cref="T:System.Data.Linq.EntityRef`1"/>.
        /// </summary>
        /// <typeparam name="TEntity">The type of the entity.</typeparam>
        /// <param name="entity">The <see cref="T:System.Data.Linq.EntityRef`1"/> to detach.</param>
        /// <returns>A new <see cref="T:System.Data.Linq.EntityRef`1"/> with the entity detached.</returns>
        protected static EntityRef<TEntity> Detach<TEntity>(EntityRef<TEntity> entity) 
            where TEntity : LinqEntityBase
        {
            if (!entity.HasLoadedOrAssignedValue)
                return new EntityRef<TEntity>();

            entity.Entity.Detach();
            return new EntityRef<TEntity>(entity.Entity);
        }
        
        /// <summary>
        /// Detach the specified lazy loaded value.
        /// </summary>
        /// <typeparam name="T">The type of the value.</typeparam>
        /// <param name="value">The lazy loaded value.</param>
        /// <returns>A new <see cref="T:System.Data.Linq.Link`1"/> that is detached.</returns>
        protected static Link<T> Detach<T>(Link<T> value)
        {
            if (!value.HasLoadedOrAssignedValue)
                return default(Link<T>);

            return new Link<T>(value.Value);
        }
        #endregion
    }
}